from .gl_tools import *
from .controls import *

import pygame
import moderngl
import numpy as np
# ~ from math import pi, sin, cos, tau
from pyrr import Matrix44, Quaternion, Vector3




pygame.init()

class Window:
    def __init__(self, size):
        self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 4)
        self.display_surface = pygame.display.set_mode((1000,800), pygame.OPENGL | pygame.DOUBLEBUF)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_PROFILE_MASK, pygame.GL_CONTEXT_PROFILE_CORE)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_FORWARD_COMPATIBLE_FLAG, True)
        self.ctx = moderngl.create_context()
        self.ctx.enable(moderngl.DEPTH_TEST)
        self.ctx.enable(moderngl.CULL_FACE)

        self.screen = pygame.Surface(size, flags=pygame.SRCALPHA)
        self.screen_texture = self.ctx.texture(self.size, 4)# 24 bit (rgba) moderngl texture
        self.screen_texture.filter = moderngl.NEAREST, moderngl.NEAREST

        # 创建quad
        p1 = V3(-1, 1, 0)
        p2 = V3(-1, -1, 0)
        p3 = V3(1, -1, 0)
        p4 = V3(1, 1, 0)
        quad_vertices = np.array([p1, p2, p3, p1, p3, p4], dtype=np.float32)
        uv1 = [0.0, 0.0]
        uv2 = [0.0, 1.0]
        uv3 = [1.0, 1.0]
        uv4 = [1.0, 0.0]
        quad_uvs = np.array([uv1, uv2, uv3, uv1, uv3, uv4, ], dtype=np.float32)
        a, b, c = quad_vertices[:,0], quad_vertices[:,1], quad_vertices[:,2]
        g, h = quad_uvs[:,0], quad_uvs[:,1]
        x = np.dstack([a, b, c, g, h])
        quad_vbo = self.ctx.buffer(x.astype("f4"))

        quad_prog = self.quad_prog = self.ctx.program(
            vertex_shader='''
#version 330 core

in vec3 in_position;
in vec2 in_texcoord_0;

uniform mat4 m_proj;
uniform mat4 m_model;
uniform mat4 m_cam;

out vec2 uv;

void main() {
    vec3 pos = vec3(in_position.x, in_position.y, in_position.z);
    gl_Position = m_proj * m_cam * m_model * vec4(pos, 1.0);

    uv = in_texcoord_0;
}
            ''',
            fragment_shader='''
#version 330 core
out vec4 fragColor;
uniform sampler2D texture0;

in vec2 uv;

void main()
{
    vec4 rgba = texture(texture0, uv);
    fragColor = vec4(rgba.bgr, rgba.a);
}

            ''',
        )

        self.quad_vao = self.ctx.simple_vertex_array(quad_prog, quad_vbo, 'in_position', 'in_texcoord_0')
        quad_m =  Matrix44.from_translation(U3(0, 0, 0)).astype("f4")
        quad_camera = Camera()
        quad_camera.proj = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, self.size[0])
        quad_camera.pos = U3(0, -1, 0)
        model1 = quad_prog['m_model']
        view1 = quad_prog['m_cam']
        proj1 = quad_prog['m_proj']
        view1.write(quad_camera.v())
        proj1.write(quad_camera.p())
        model1.write(quad_m)
        # quad创建完成

        self.camera = Camera(U3(0, 0, 0))   # 默认摄像机
        self.draw3d_list = []

        self.draw2d_list = []
        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []    # 一般只使用第一个元素
        self.mouse0_flag = False

        self.update_callback = []



    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == pygame.MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e)
        elif e.type == pygame.KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e)
                if r == True:
                    break


    def render(self):
        self.screen.fill((0, 0, 0, 0))
        for control in self.draw2d_list:
            control.draw(self)

        self.quad_prog['texture0'].value = 0
        self.screen_texture.use()
        texture_data = self.screen.get_view('1')      # 这个接口真变态啊
        self.screen_texture.write(texture_data)

        self.ctx.enable(moderngl.BLEND)
        self.ctx.clear(1.0, 1.0, 1.0)
        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光
        self.quad_vao.render(moderngl.TRIANGLES)
        self.ctx.disable(moderngl.BLEND)

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt / 1000)



class Window2:
    def __init__(self, size):
        self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 4)
        self.display_surface = pygame.display.set_mode((1000,800), pygame.OPENGL | pygame.DOUBLEBUF)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_PROFILE_MASK, pygame.GL_CONTEXT_PROFILE_CORE)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_FORWARD_COMPATIBLE_FLAG, True)
        self.ctx = moderngl.create_context()
        self.ctx.enable(moderngl.DEPTH_TEST)
        self.ctx.enable(moderngl.CULL_FACE)
        self.fbo = self.ctx.screen

        self.screen = pygame.Surface(size, flags=pygame.SRCALPHA)
        self.screen_texture = self.ctx.texture(self.size, 4)# 24 bit (rgba) moderngl texture
        self.screen_texture.filter = moderngl.NEAREST, moderngl.NEAREST

        # 创建quad
        p1 = V3(-1, 1, 0)
        p2 = V3(-1, -1, 0)
        p3 = V3(1, -1, 0)
        p4 = V3(1, 1, 0)
        quad_vertices = np.array([p1, p2, p3, p1, p3, p4], dtype=np.float32)
        uv1 = [0.0, 0.0]
        uv2 = [0.0, 1.0]
        uv3 = [1.0, 1.0]
        uv4 = [1.0, 0.0]
        quad_uvs = np.array([uv1, uv2, uv3, uv1, uv3, uv4, ], dtype=np.float32)
        a, b, c = quad_vertices[:,0], quad_vertices[:,1], quad_vertices[:,2]
        g, h = quad_uvs[:,0], quad_uvs[:,1]
        x = np.dstack([a, b, c, g, h])
        quad_vbo = self.ctx.buffer(x.astype("f4"))


        quad_prog_data = {
            "vertex_shader":'''
#version 330 core

in vec3 in_position;
in vec2 in_texcoord_0;

uniform mat4 m_proj;
uniform mat4 m_model;
uniform mat4 m_cam;

out vec2 uv;

void main() {
    vec3 pos = vec3(in_position.x, in_position.y, in_position.z);
    gl_Position = m_proj * m_cam * m_model * vec4(pos, 1.0);

    uv = in_texcoord_0;
}
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 fragColor;
uniform sampler2D texture0;

in vec2 uv;

void main()
{
    vec4 rgba = texture(texture0, uv);
    fragColor = vec4(rgba.bgr, rgba.a);
}

            ''',
        }

        quad2_prog_data = {
            "vertex_shader":'''
#version 330 core

in vec3 in_position;
in vec2 in_texcoord_0;

uniform mat4 m_proj;
uniform mat4 m_model;
uniform mat4 m_cam;

out vec2 uv;

void main() {
    vec3 pos = vec3(in_position.x, in_position.y, in_position.z);
    gl_Position = m_proj * m_cam * m_model * vec4(pos, 1.0);
    uv = vec2(in_texcoord_0.x, 1 - in_texcoord_0.y);
}
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 fragColor;
//uniform sampler2D texture0;
uniform sampler2D normal_texture;
uniform sampler2D tMap;
uniform vec2 uResolution;
uniform float fovy;


in vec2 uv;
//in vec3 keep_xyz;

//void main()
//{
//    vec4 rgba = texture(texture0, uv + vec2(0.0005, 0.0005));
//    fragColor = rgba;
//}




vec4 fxaa(sampler2D tex, vec2 uv, vec2 resolution) {
    vec2 pixel = vec2(1) / resolution;
    vec3 l = vec3(0.299, 0.587, 0.114);
    float lNW = dot(texture2D(tex, uv + vec2(-1, -1) * pixel).rgb, l);
    float lNE = dot(texture2D(tex, uv + vec2( 1, -1) * pixel).rgb, l);
    float lSW = dot(texture2D(tex, uv + vec2(-1,  1) * pixel).rgb, l);
    float lSE = dot(texture2D(tex, uv + vec2( 1,  1) * pixel).rgb, l);
    float lM  = dot(texture2D(tex, uv).rgb, l);
    float lMin = min(lM, min(min(lNW, lNE), min(lSW, lSE)));
    float lMax = max(lM, max(max(lNW, lNE), max(lSW, lSE)));

    vec2 dir = vec2(
        -((lNW + lNE) - (lSW + lSE)),
        ((lNW + lSW) - (lNE + lSE))
    );

    float dirReduce = max((lNW + lNE + lSW + lSE) * 0.03125, 0.0078125);
    float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
    dir = min(vec2(8, 8), max(vec2(-8, -8), dir * rcpDirMin)) * pixel;

    vec3 rgbA = 0.5 * (
        texture2D(tex, uv + dir * (1.0 / 3.0 - 0.5)).rgb +
        texture2D(tex, uv + dir * (2.0 / 3.0 - 0.5)).rgb);

    vec3 rgbB = rgbA * 0.5 + 0.25 * (
        texture2D(tex, uv + dir * -0.5).rgb +
        texture2D(tex, uv + dir * 0.5).rgb);

    float lB = dot(rgbB, l);

    return mix(
        vec4(rgbB, 1),
        vec4(rgbA, 1),
        max(sign(lB - lMin), 0.0) * max(sign(lB - lMax), 0.0)
    );
}


vec4 alphaBlend(vec4 top, vec4 bottom)
{
    vec3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
    float alpha = top.a + bottom.a * (1 - top.a);

    return vec4(color, alpha);
}
float LinearizeDepth(float depth)
{
    float near = 0.1;
    float far  = 1000.0;
    float z = depth * 2.0 - 1.0;
    return (2.0 * near * far) / (far + near - z * (far - near));
}
float T1(float depth)
{
    float near = 0.1;
    float far  = 1000.0;
    return LinearizeDepth(depth)/ far;
}
vec4 Frag(vec2 uv)
{
    float _Scale = 1;
    vec4 _Color = vec4(0.0, 0.0, 0.0, 1.0);
    //float _DepthThreshold = 1.5;
    float _DepthThreshold = 2;

    float _DepthNormalThreshold = 3;
    float _DepthNormalThresholdScale = 5;

    float _NormalThreshold = 0.1;

    float halfScaleFloor = floor(_Scale * 0.5);
    float halfScaleCeil = ceil(_Scale * 0.5);

    vec2 TexelSize = vec2(1 / uResolution.x, 1 / uResolution.y);

    vec2 bottomLeftUV = uv - TexelSize * halfScaleFloor;
    vec2 topRightUV = uv + TexelSize * halfScaleCeil;
    vec2 bottomRightUV = uv + vec2(TexelSize.x * halfScaleCeil, -TexelSize.y * halfScaleFloor);
    vec2 topLeftUV = uv + vec2(-TexelSize.x * halfScaleFloor, TexelSize.y * halfScaleCeil);

    vec4 p_i = texture2D(normal_texture, uv);
    vec4 p0 = texture2D(normal_texture, bottomLeftUV);
    vec4 p1 = texture2D(normal_texture, topRightUV);
    vec4 p2 = texture2D(normal_texture, bottomRightUV);
    vec4 p3 = texture2D(normal_texture, topLeftUV);
    vec3 normalp = p_i.rgb;
    vec3 normal0 = p0.rgb;
    vec3 normal1 = p1.rgb;
    vec3 normal2 = p2.rgb;
    vec3 normal3 = p3.rgb;
    //float depthp = p_i.a;
    //float depth0 = p0.a;
    //float depth1 = p1.a;
    //float depth2 = p2.a;
    //float depth3 = p3.a;
    float depthp = LinearizeDepth(p_i.a);
    float depth0 = LinearizeDepth(p0.a);
    float depth1 = LinearizeDepth(p1.a);
    float depth2 = LinearizeDepth(p2.a);
    float depth3 = LinearizeDepth(p3.a);
    vec3 viewNormal = normal0 * 2 - 1;

    vec2 temp5 = uv * 2 - 1;
    temp5 = vec2(temp5.x * uResolution.x / uResolution.y, temp5.y);
    temp5 = temp5 * tan(fovy / 2);
    vec3 keep_xyz = normalize(vec3(temp5, -1));
    //float NdotV = 1 - acos(dot(viewNormal, -keep_xyz));  // angle of plane and eyelight
    float myth = length(viewNormal) >1.6 ? 0 : acos(dot(viewNormal, -keep_xyz));  // angle of plane normal and eyelight
    myth = clamp(myth, 0, 1.5);
    // if empty there return 0

    //float normalThreshold01 = clamp((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold), 0.0, 1.0);
    //float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;

    float normalThreshold = tan(myth);
    float depthThreshold = _DepthThreshold * depth0 * normalThreshold * 0.1 + 0.01;

    float depthFiniteDifference0 = depth1 - depth0;
    float depthFiniteDifference1 = depth3 - depth2;

    float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 3 ;
    float edgeDepth2 = edgeDepth > depthThreshold ? 1 : 0;

    vec3 normalFiniteDifference0 = normal1 - normal0;
    vec3 normalFiniteDifference1 = normal3 - normal2;
    float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
    edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;

    float edge = max(edgeDepth2, edgeNormal);
    //edge = edge * 0 +edgeDepth;
    //edge = edge * 0 +depthThreshold;
    //edge = edge * 0 + myth / 1.5;
    vec4 edgeColor = vec4(_Color.rgb, _Color.a * edge);
    vec4 color = texture2D(tMap, uv);

    //vec4 help = vec4(edgeDepth, edgeDepth, edgeDepth, 1);
    //return edgeColor + color * 0;
    //return edgeColor * 0 + color * 0 + vec4(viewNormal * 0.5 + 0.5, 1);
    //return edgeColor * 0 + color * 0 + vec4(keep_xyz * 0.5 + 0.5, 1);
    //return edgeColor * 0 + color * 0 + vec4(uv.x, uv.y, 0, 1);
    return alphaBlend(edgeColor, color);
    //return alphaBlend(edgeColor, color) * 0 + help;
}
void main() {
    //vec4 aa = fxaa(tMap, uv, uResolution);
    vec4 side = Frag(uv);
    //fragColor = side;
    fragColor = side * 1 + texture2D(tMap, uv) * 0;
}

''',
        }
        """
        bug：过于倾斜的平面，检出了edgeDepth2
        normalThreshold总是大于1的
        """
        quad_prog = self.quad_prog = self.ctx.program(**quad_prog_data)
        self.quad_vao = self.ctx.simple_vertex_array(quad_prog, quad_vbo, 'in_position', 'in_texcoord_0')
        quad_m =  Matrix44.from_translation(U3(0, 0, 0)).astype("f4")
        quad_camera = Camera(U3(0, -1, 0))
        quad_camera.proj = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, self.size[0])
        quad_camera.pos = U3(0, 0, 0)
        model1 = quad_prog['m_model']
        view1 = quad_prog['m_cam']
        proj1 = quad_prog['m_proj']
        view1.write(quad_camera.v())
        proj1.write(quad_camera.p())
        model1.write(quad_m)
        # quad创建完成
        # 创建quad2
        self.screen2_texture = self.ctx.texture(self.size, 4)
        # ~ self.screen2_texture.filter = moderngl.LINEAR, moderngl.LINEAR
        self.depth_texture = self.ctx.depth_texture(self.size)
        self.fbo2 = self.ctx.framebuffer( self.screen2_texture, self.depth_texture)
        quad2_m =  Matrix44.from_translation(U3(0, 1, 0)).astype("f4")
        quad2_prog = self.quad2_prog = self.ctx.program(**quad2_prog_data)
        self.quad2_vao = self.ctx.simple_vertex_array(quad2_prog, quad_vbo, 'in_position', 'in_texcoord_0')
        model2 = quad2_prog['m_model']
        view2 = quad2_prog['m_cam']
        proj2 = quad2_prog['m_proj']
        _ = np.array(self.size, dtype="f4")
        print(_)
        quad2_prog['uResolution'].write(_)
        quad2_prog['fovy'].value = 60 / 180 * pi                # 这个参数跟随相机！
        view2.write(quad_camera.v())
        proj2.write(quad_camera.p())
        model2.write(quad2_m)
        self.normal_texture = self.ctx.texture(self.size, 4, dtype="f4")
        self.depth2_texture = self.ctx.depth_texture(self.size)
        self.fbo3 = self.ctx.framebuffer(self.normal_texture, self.depth2_texture)

        #quad2创建完成


        self.camera = Camera(U3(0, 0, 0))   # 默认摄像机
        self.draw3d_list = []

        self.draw2d_list = []
        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []    # 一般只使用第一个元素
        self.update_callback = []


    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == pygame.MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e)
        elif e.type == pygame.KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e)
                if r == True:
                    break

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt / 1000)


    def render(self):
        #2d
        self.screen.fill((0, 0, 0, 0))
        for control in self.draw2d_list:
            control.draw(self)

        #3d
        self.quad_prog['texture0'].value = 0
        self.screen_texture.use()
        texture_data = self.screen.get_view('1')      # 这个接口真变态啊
        self.screen_texture.write(texture_data)

        #先输出法线到normal_texture
        self.fbo3.use()
        self.ctx.clear(1.0, 1.0, 1.0)
        for obj in self.draw3d_list:
            obj.draw_normal(self.camera)

        #输出正常图像到screen2_texture
        self.fbo2.use()
        self.ctx.clear(1.0, 1.0, 1.0)
        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光


        # 融合
        self.fbo.use()
        self.quad2_prog['normal_texture'].value = 2
        self.normal_texture.use(2)
        self.quad2_prog['tMap'].value = 4
        self.screen2_texture.use(4)
        self.ctx.enable(moderngl.BLEND)
        self.ctx.clear(1.0, 1.0, 1.0)
        self.quad2_vao.render(moderngl.TRIANGLES)       # 渲染quad2（将对screen2_texture进一步处理）
        self.quad_vao.render(moderngl.TRIANGLES)
        self.ctx.disable(moderngl.BLEND)


class Window3:
    def __init__(self, size):
        self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 4)
        self.display_surface = pygame.display.set_mode((1000,800), pygame.OPENGL | pygame.DOUBLEBUF)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MAJOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_MINOR_VERSION, 3)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_PROFILE_MASK, pygame.GL_CONTEXT_PROFILE_CORE)
        #~ pygame.display.gl_set_attribute(pygame.GL_CONTEXT_FORWARD_COMPATIBLE_FLAG, True)
        self.ctx = moderngl.create_context()
        self.ctx.enable(moderngl.DEPTH_TEST)
        self.ctx.enable(moderngl.CULL_FACE)
        self.fbo = self.ctx.screen

        self.screen = pygame.Surface(size, flags=pygame.SRCALPHA)
        self.screen_texture = self.ctx.texture(self.size, 4)# 24 bit (rgba) moderngl texture
        self.screen_texture.filter = moderngl.NEAREST, moderngl.NEAREST


        screen_prog_data = {
            "vertex_shader":'''
#version 330 core

in vec3 in_position;
in vec2 in_texcoord_0;

uniform mat4 all_mat;

out vec2 uv;

void main() {
    gl_Position = all_mat * vec4(in_position, 1.0);

    uv = vec2(in_texcoord_0.x, in_texcoord_0.y);
}
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 fragColor;
uniform sampler2D input_texture;

in vec2 uv;

void main()
{
    vec4 rgba = texture(input_texture, uv);
    fragColor = vec4(rgba.bgr, rgba.a);
}

            ''',
        }

        outline_prog_data = {
            "vertex_shader":'''
#version 330 core

in vec3 in_position;
in vec2 in_texcoord_0;

uniform mat4 all_mat;

out vec2 uv;

void main() {
    gl_Position = all_mat * vec4(in_position, 1.0);
    uv = vec2(in_texcoord_0.x, 1 - in_texcoord_0.y);
}
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 fragColor;
uniform sampler2D input_texture1;
uniform sampler2D input_texture2;
uniform vec2 uResolution;
uniform float fovy;


in vec2 uv;




vec4 fxaa(sampler2D tex, vec2 uv, vec2 resolution) {
    vec2 pixel = vec2(1) / resolution;
    vec3 l = vec3(0.299, 0.587, 0.114);
    float lNW = dot(texture2D(tex, uv + vec2(-1, -1) * pixel).rgb, l);
    float lNE = dot(texture2D(tex, uv + vec2( 1, -1) * pixel).rgb, l);
    float lSW = dot(texture2D(tex, uv + vec2(-1,  1) * pixel).rgb, l);
    float lSE = dot(texture2D(tex, uv + vec2( 1,  1) * pixel).rgb, l);
    float lM  = dot(texture2D(tex, uv).rgb, l);
    float lMin = min(lM, min(min(lNW, lNE), min(lSW, lSE)));
    float lMax = max(lM, max(max(lNW, lNE), max(lSW, lSE)));

    vec2 dir = vec2(
        -((lNW + lNE) - (lSW + lSE)),
        ((lNW + lSW) - (lNE + lSE))
    );

    float dirReduce = max((lNW + lNE + lSW + lSE) * 0.03125, 0.0078125);
    float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
    dir = min(vec2(8, 8), max(vec2(-8, -8), dir * rcpDirMin)) * pixel;

    vec3 rgbA = 0.5 * (
        texture2D(tex, uv + dir * (1.0 / 3.0 - 0.5)).rgb +
        texture2D(tex, uv + dir * (2.0 / 3.0 - 0.5)).rgb);

    vec3 rgbB = rgbA * 0.5 + 0.25 * (
        texture2D(tex, uv + dir * -0.5).rgb +
        texture2D(tex, uv + dir * 0.5).rgb);

    float lB = dot(rgbB, l);

    return mix(
        vec4(rgbB, 1),
        vec4(rgbA, 1),
        max(sign(lB - lMin), 0.0) * max(sign(lB - lMax), 0.0)
    );
}


vec4 alphaBlend(vec4 top, vec4 bottom)
{
    vec3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
    float alpha = top.a + bottom.a * (1 - top.a);

    return vec4(color, alpha);
}
float LinearizeDepth(float depth)
{
    float near = 0.1;
    float far  = 1000.0;
    float z = depth * 2.0 - 1.0;
    return (2.0 * near * far) / (far + near - z * (far - near));
}
float T1(float depth)
{
    float near = 0.1;
    float far  = 1000.0;
    return LinearizeDepth(depth)/ far;
}
vec4 Frag(sampler2D tMap, sampler2D normal_texture, vec2 uv)
{
    float _Scale = 1;
    vec4 _Color = vec4(0.0, 0.0, 0.0, 1.0);
    //float _DepthThreshold = 1.5;
    //float _DepthThreshold = 2;
    float _DepthThreshold = 0.01;

    float _DepthNormalThreshold = 3;
    float _DepthNormalThresholdScale = 5;

    float _NormalThreshold = 0.1;

    float halfScaleFloor = floor(_Scale * 0.5);
    float halfScaleCeil = ceil(_Scale * 0.5);

    vec2 TexelSize = vec2(1 / uResolution.x, 1 / uResolution.y);

    vec2 bottomLeftUV = uv - TexelSize * halfScaleFloor;
    vec2 topRightUV = uv + TexelSize * halfScaleCeil;
    vec2 bottomRightUV = uv + vec2(TexelSize.x * halfScaleCeil, -TexelSize.y * halfScaleFloor);
    vec2 topLeftUV = uv + vec2(-TexelSize.x * halfScaleFloor, TexelSize.y * halfScaleCeil);

    vec4 p_i = texture2D(normal_texture, uv);
    vec4 p0 = texture2D(normal_texture, bottomLeftUV);
    vec4 p1 = texture2D(normal_texture, topRightUV);
    vec4 p2 = texture2D(normal_texture, bottomRightUV);
    vec4 p3 = texture2D(normal_texture, topLeftUV);
    vec3 normalp = p_i.rgb;
    vec3 normal0 = p0.rgb;
    vec3 normal1 = p1.rgb;
    vec3 normal2 = p2.rgb;
    vec3 normal3 = p3.rgb;
    //float depthp = p_i.a;
    //float depth0 = p0.a;
    //float depth1 = p1.a;
    //float depth2 = p2.a;
    //float depth3 = p3.a;
    float depthp = LinearizeDepth(p_i.a);
    float depth0 = LinearizeDepth(p0.a);
    float depth1 = LinearizeDepth(p1.a);
    float depth2 = LinearizeDepth(p2.a);
    float depth3 = LinearizeDepth(p3.a);
    vec3 viewNormal = normalp * 2 - 1;

    vec2 temp5 = uv * 2 - 1;
    temp5 = vec2(temp5.x * uResolution.x / uResolution.y, temp5.y);
    temp5 = temp5 * tan(fovy / 2);
    vec3 keep_xyz = normalize(vec3(temp5, -1));
    //float NdotV = 1 - acos(dot(viewNormal, -keep_xyz));  // angle of plane and eyelight
    float myth = length(viewNormal) >1.6 ? 0 : acos(dot(viewNormal, -keep_xyz));  // angle of plane normal and eyelight
    myth = clamp(myth, 0, 1.5707);
    // if empty there return 0

    //float normalThreshold01 = clamp((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold), 0.0, 1.0);
    //float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;

    float normalThreshold = tan(myth) + 0.01;
    //float depthThreshold = _DepthThreshold * depth0 * normalThreshold * 0.1 + 0.01;
    //float depthThreshold = _DepthThreshold * depth0 * normalThreshold /3;
    float depthThreshold = _DepthThreshold / cos(myth) + depth0 * tan(max(myth, 0.7)) * TexelSize.x * 2 ;

    float depthFiniteDifference0 = depth1 - depth0;
    float depthFiniteDifference1 = depth3 - depth2;

    float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2))   ;
    float edgeDepth2 = edgeDepth > depthThreshold ? 1 : 0;

    vec3 normalFiniteDifference0 = normal1 - normal0;
    vec3 normalFiniteDifference1 = normal3 - normal2;
    float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
    edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;

    float edge = max(edgeDepth2, edgeNormal);
    //edge = edge * 0 + 0.5;
    //edge = edge * 0 + (p_i.w * 2 - 1);
    //edge = edge * 0 + edgeNormal;
    //edge = edge * 0 +edgeDepth *10;
    //edge = edge * 0 +depthThreshold * 10;
    //edge = edge * 0 +(edgeDepth - depthThreshold) * 10;
    //edge = edge * 0 + myth / 1.5;
    vec4 edgeColor = vec4(_Color.rgb, _Color.a * edge);
    //vec4 edgeColor = vec4(_Color.rgb, _Color.a * edge) * 0 + vec4(1, 0, 0, 1);
    vec4 color = texture2D(tMap, uv);

    //vec4 help = vec4(edgeDepth, edgeDepth, edgeDepth, 1);
    //return edgeColor + color * 0;
    //return edgeColor * 0 + color * 0 + vec4(viewNormal * 0.5 + 0.5, 1);
    //return edgeColor * 0 + color * 0 + vec4(keep_xyz * 0.5 + 0.5, 1);
    //return edgeColor * 0 + color * 0 + vec4(uv.x, uv.y, 0, 1);
    return alphaBlend(edgeColor, color);
    //return alphaBlend(edgeColor, color) * 0 + help;
}
void main() {
    //vec4 aa = fxaa(input_texture1, uv, uResolution);
    vec4 side = Frag(input_texture1, input_texture2,  uv);
    fragColor = side;
}

''',
        }
        self.fbo2 = EasyFbo(self.size, self.ctx)     # 图像渲染
        self.fbo3 = EasyFbo(self.size, self.ctx, quality=True)     # 法线渲染

        self.passj = PassJust(self.size, self.ctx, self.ctx.program(**screen_prog_data), self.screen_texture, layer=2)
        self.pass1 = PassCons(self.size, self.ctx, self.ctx.program(**outline_prog_data), self.fbo2, self.fbo3)
        _ = np.array(self.size, dtype="f4")
        self.pass1.prog['uResolution'].write(_)
        self.pass1.prog['fovy'].value = 60 / 180 * pi


        self.camera = Camera(U3(0, 0, 0))   # 默认摄像机
        self.draw3d_list = []

        self.draw2d_list = []
        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []    # 一般只使用第一个元素
        self.update_callback = []


    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == pygame.MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e)
        elif e.type == pygame.KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e)
                if r == True:
                    break
        elif e.type == pygame.KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e)
                if r == True:
                    break

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt / 1000)

    def render(self):
        #2d
        self.screen.fill((0, 0, 0, 0))
        for control in self.draw2d_list:
            control.draw(self)
        texture_data = self.screen.get_view('1')      # 这个接口真变态啊
        self.screen_texture.write(texture_data)

        #先输出法线到normal_texture
        self.fbo3.use()
        self.ctx.clear(1.0, 1.0, 1.0)
        for obj in self.draw3d_list:
            obj.draw_normal(self.camera)

        #输出正常图像到screen2_texture
        # ~ self.fbo2.use()
        self.fbo2.use()
        self.ctx.clear(1.0, 1.0, 1.0)
        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光


        # 融合


        self.fbo.use()
        self.ctx.enable(moderngl.BLEND)
        self.ctx.clear(1.0, 1.0, 1.0)

        self.pass1.render()     # 绘制3d
        self.passj.render()     # 绘制gui

        self.ctx.disable(moderngl.BLEND)


